#include "OgreGLESTexture.h"
#include "OgreGLESSupport.h"
#include "OgreGLESPixelFormat.h"
#include "OgreGLESRenderSystem.h"
#include "OgreGLESHardwarePixelBuffer.h"

#include "OgreRoot.h"

namespace Ogre
{
	static inline void doImageIO(const String& name, const String& group,
								 const String& ext,
								 std::vector<Image>& images,
								 Resource* r)
	{
		size_t imgIdx = images.size();
		images.push_back(Image());
		
		DataStreamPtr dstream = 
			ResourceGroupManager::getSingleton().openResource(
				name, group, true, r);
		
		images[imgIdx].load(dstream, ext);
	}
	
	GLESTexture::GLESTexture(ResourceManager* creator, const String& name,
							 ResourceHandle handle, const String& group, bool isManual,
							 ManualResourceLoader* loader, GLESSupport& support)
		: Texture(creator, name, handle, group, isManual, loader),
		  mTextureID(0), mGLSupport(support)
	{
	}
	
	GLESTexture::~GLESTexture()
	{
		if (isLoaded())
		{
			unload();
		}
		else
		{
			freeInternalResources();
		}
	}
	
	GLenum GLESTexture::getGLESTextureTarget(void) const
	{
		return GL_TEXTURE_2D;
	}
	
	void GLESTexture::createInternalResourcesImpl(void)
	{
		mWidth = GLESPixelUtil::optionalPO2(mWidth);
		mHeight = GLESPixelUtil::optionalPO2(mHeight);
		mDepth = GLESPixelUtil::optionalPO2(mDepth);
		
		mFormat = TextureManager::getSingleton().getNativeFormat(TEX_TYPE_2D, mFormat, mUsage);
		
		size_t maxMips = GLESPixelUtil::getMaxMipmaps(mWidth, mHeight, mDepth, mFormat);
		
		if (PixelUtil::isCompressed(mFormat) && (mNumMipmaps == 0))
			mNumRequestedMipmaps = 0;
			
		mNumMipmaps = mNumRequestedMipmaps;
		if (mNumMipmaps > maxMips)
			mNumMipmaps = maxMips;
			
		glGenTextures(1, &mTextureID);
		GL_CHECK_ERROR;
		
		glBindTexture(GL_TEXTURE_2D, mTextureID);
		GL_CHECK_ERROR;
		
		glTexParameteri(GL_TEXTURE_2D,
						GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
		GL_CHECK_ERROR;
		glTexParameteri(GL_TEXTURE_2D,
						GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GL_CHECK_ERROR;
		glTexParameteri(GL_TEXTURE_2D,
						GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		GL_CHECK_ERROR;
		glTexParameteri(GL_TEXTURE_2D,
						GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		GL_CHECK_ERROR;
		
		mMipmapsHardwareGenerated = 
			Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(RSC_AUTOMIPMAP) && !PixelUtil::isCompressed(mFormat);
			
		if ((mUsage & TU_AUTOMIPMAP) &&
			mNumRequestedMipmaps && mMipmapsHardwareGenerated)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
			GL_CHECK_ERROR;
		}
		
		GLenum format = GLESPixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma);
		size_t width = mWidth;
		size_t height = mHeight;
		size_t depth = mDepth;
		
		if (PixelUtil::isCompressed(mFormat))
		{
			size_t size = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
			
			uint8* tmpdata = OGRE_NEW_FIX_FOR_WIN32 uint8[size];
			memset(tmpdata, 0, size);
			for (size_t mip = 0; mip <= mNumMipmaps; mip++)
			{
				size = PixelUtil::getMemorySize(width, height, depth, mFormat);
				
				glCompressedTexImage2D(GL_TEXTURE_2D,
									   mip,
									   format,
									   width, height,
									   0,
									   size,
									   tmpdata);
				GL_CHECK_ERROR;
				
				if (width > 1)
				{
					width = width / 2;
				}
				if (height > 1)
				{
					height = height / 2;
				}
				if (depth > 1)
				{
					depth = depth / 2;
				}
			}
			OGRE_DELETE[] tmpdata;
		}
		else
		{
			for (size_t mip = 0; mip <= mNumMipmaps; mip++)
			{
				glTexImage2D(GL_TEXTURE_2D,
							 mip, 
							 format,
							 width, height,
							 0, 
							 format,
							 GL_UNSIGNED_BYTE, 0);
				GL_CHECK_ERROR;
				
				if (width > 1)
				{
					width = width / 2;
				}
				if (height > 1)
				{
					height = height / 2;
				}
			}
		}
		
		_createSurfaceList();
		
		mFormat = getBuffer(0, 0)->getFormat();
	}
	
	void GLESTexture::createRenderTexture(void)
	{
		createInternalResources();
	}
	
	void GLESTexture::prepareImpl()
	{
		if (mUsage & TU_RENDERTARGET)
			return;
			
		String baseName, ext;
		size_t pos = mName.find_last_of(".");
		baseName = mName.substr(0, pos);
		
		if (pos != String::npos)
		{
			ext = mName.substr(pos + 1);
		}
		
		LoadedImages loadedImages = LoadedImages(OGRE_NEW_FIX_FOR_WIN32 std::vector<Image>());
		
		if (mTextureType == TEX_TYPE_2D)
		{
			doImageIO(mName, mGroup, ext, *loadedImages, this);
			
			if ((*loadedImages)[0].getDepth() > 1)
			{
				OGRE_EXCEPT(Exception::ERR_NOT_IMPLEMENTED,
							"**** Unsupported 3D texture type ****",
							"GLESTexture::prepare");
			}
			
			PixelFormat imageFormat = (*loadedImages)[0].getFormat();
			if (imageFormat == PF_PVRTC_RGB2 || imageFormat == PF_PVRTC_RGBA2 ||
			    imageFormat == PF_PVRTC_RGB4 || imageFormat == PF_PVRTC_RGBA4)
			{
				size_t imageMips = (*loadedImages)[0].getNumMipmaps();
				if (imageMips == 0)
				{
					mNumMipmaps = mNumRequestedMipmaps = imageMips;
					mUsage &= ~TU_AUTOMIPMAP;
				}
			}
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_NOT_IMPLEMENTED,
						"**** Unknown texture type ****",
						"GLESTexture::prepare");
		}
		
		mLoadedImages = loadedImages;
	}
	
	void GLESTexture::unprepareImpl()
	{
		mLoadedImages.setNull();
	}
	
	void GLESTexture::loadImpl()
	{
		if (mUsage & TU_RENDERTARGET)
		{
			createRenderTexture();
			return;
		}
		
		LoadedImages loadedImages = mLoadedImages;
		mLoadedImages.setNull();
		
		ConstImagePtrList imagePtrs;
		
		for (size_t i = 0; i < loadedImages->size(); ++i)
		{
			imagePtrs.push_back(&(*loadedImages)[i]);
		}
		
		_loadImages(imagePtrs);
	}
	
	void GLESTexture::freeInternalResourcesImpl()
	{
		mSurfaceList.clear();
		glDeleteTextures(1, &mTextureID);
		GL_CHECK_ERROR;
	}
	
	void GLESTexture::_createSurfaceList()
	{
		mSurfaceList.clear();
		
		bool wantGeneratedMips = (mUsage & TU_AUTOMIPMAP) != 0;
		
		bool doSoftware = wantGeneratedMips && !mMipmapsHardwareGenerated && getNumMipmaps();
		
		for (size_t face = 0; face < getNumFaces(); face++)
		{
			size_t width = mWidth;
			size_t height = mHeight;
			for (size_t mip = 0; mip <= getNumMipmaps(); mip++)
			{
				GLESHardwarePixelBuffer* buf = OGRE_NEW GLESTextureBuffer(mName,
																getGLESTextureTarget(),
																mTextureID,
																width, height,
																GLESPixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma),
																face,
																mip,
																static_cast<HardwareBuffer::Usage>(mUsage),
																doSoftware && mip == 0, mHwGamma, mFSAA);
				
				mSurfaceList.push_back(HardwarePixelBufferSharedPtr(buf));
				
				if (mFormat == PF_PVRTC_RGB2 || mFormat == PF_PVRTC_RGBA2 ||
					mFormat == PF_PVRTC_RGB4 || mFormat == PF_PVRTC_RGBA4)
				{
					if (width > 1)
					{
						width = width / 2;
					}
					if (height > 1)
					{
						height = height / 2;
					}
				}
				
				if (buf->getWidth() == 0 ||
					buf->getHeight() == 0 ||
					buf->getDepth() == 0)
				{
					OGRE_EXCEPT(
						Exception::ERR_RENDERINGAPI_ERROR,
						"Zero sized texture surface on texture " + getName() + 
						" face " + StringConverter::toString(face) +
						" mipmap " + StringConverter::toString(mip) +
						". The GL probably driver refused to create the texture.",
						"GLESTexture::_createSurfaceList");
				}
			}
		}
	}
	
	HardwarePixelBufferSharedPtr GLESTexture::getBuffer(size_t face, size_t mipmap)
	{
		if (face >= getNumFaces())
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
						"Face index out of range",
						"GLESTexture::getBuffer");
		}
		
		if (mipmap > mNumMipmaps)
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
						"Mipmap index out of range",
						"GLESTexture::getBuffer");
		}
		
		unsigned int idx = face * (mNumMipmaps + 1) + mipmap;
		assert(idx < mSurfaceList.size());
		return mSurfaceList[idx];
	}
}
